﻿using System.Collections.Generic;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework;

namespace RaptorEngine.Managers
{

    /// <summary>
    ///  An enum that represents the "logical" player
    /// </summary>
    public enum LogicalPlayerIndex
    {
        Any = -1,
        One = PlayerIndex.One,
        Two,
        Three,
        Four
    }

    /// <summary>
    ///  An abstract class that is used to 
    ///  make key and button-presses abstract.
    /// </summary>
    public abstract class InputCommand
    {
        public bool IsActive { get; private set; }

        internal void Detect()
        {
            IsActive = OnDetect();
        }

        /// <summary>
        ///  Make this method return whether the 
        ///  input command is active.
        /// </summary>
        protected abstract bool OnDetect();
    }

    /// <summary>
    ///  A class used to poll the state of input devices.
    ///  It provides easy to use methods to check different
    ///  states of both keyboard and gamepads. It's also
    ///  possible to retrieve the current state of the mouse.
    /// </summary>
    public class InputManager
    {
        //Current keyboard state
        private Dictionary<int, Keys> _pressedKeys = new Dictionary<int, Keys>();
        //Previous keyboard state
        private Dictionary<int, Keys> _prevPressedKeys = new Dictionary<int, Keys>();
        //Current states of all connected gamepads
        private Dictionary<PlayerIndex, GamePadState> _gamepadStates = new Dictionary<PlayerIndex, GamePadState>();
        //Previous states of all connected gamepads
        private Dictionary<PlayerIndex, GamePadState> _prevGamepadStates = new Dictionary<PlayerIndex, GamePadState>();
        //Describes which logical player who uses which controller. Eg if logical player 1 is using controller 3
        private Dictionary<LogicalPlayerIndex, PlayerIndex> _logicalPlayers = new Dictionary<LogicalPlayerIndex, PlayerIndex>();

        private Dictionary<string, InputCommand> _commands = new Dictionary<string, InputCommand>();

        private MouseState mouseState;

        internal void Update()
        {
            mouseState = Mouse.GetState();

            //Handle keyboard input
            _prevPressedKeys = new Dictionary<int,Keys>(_pressedKeys);
            _pressedKeys.Clear();

            foreach(Keys key in Keyboard.GetState().GetPressedKeys())
            {
                _pressedKeys.Add(key.GetHashCode(), key);
            }

            //Handle gamepad input
            _prevGamepadStates = new Dictionary<PlayerIndex, GamePadState>(_gamepadStates);
            _gamepadStates.Clear();

            for(PlayerIndex i = PlayerIndex.One; i <= PlayerIndex.Four; i++)
            {
                //Check if controller is a connected gamepad
                if (GamePad.GetCapabilities(i).IsConnected && GamePad.GetCapabilities(i).GamePadType == GamePadType.GamePad)
                {
                    _gamepadStates.Add(i, GamePad.GetState(i));
                }
                //Check if used controller is disconnected
                else if (_prevGamepadStates.ContainsKey(i) && _logicalPlayers.ContainsValue(i))
                {
                    //Errormanager.GamepadDisconnected(i);
                }
            }

            //Handle commands
            foreach (KeyValuePair<string, InputCommand> command in _commands)
            {
                command.Value.Detect();
            }

        }

        /// <summary>
        /// Used to check if a specified gamepad is plugged in.
        /// </summary>
        /// <param name="gamepad">The gamedpad to check</param>
        public bool GamePadPluggedIn(PlayerIndex gamepad)
        {
            return _gamepadStates.ContainsKey(gamepad);
        }

        /// <summary>
        /// Used to check if the gamepad of a logical player is connected.
        /// </summary>
        /// <param name="player">A logical player i.e. Player One</param>
        public bool GamePadPluggedIn(LogicalPlayerIndex player)
        {
            if (_logicalPlayers.ContainsKey(player))
            {
                return _gamepadStates.ContainsKey(_logicalPlayers[player]);
            }
            return false;
        }

        /// <summary>
        ///  Return the current mouse state
        /// </summary>
        public MouseState GetMouseState()
        {
            return mouseState;
        }

        /// <summary>
        ///  Sets which logical player who is in control of a gamepad, 
        ///  for example if player 1 uses gamepad 2.
        /// </summary>
        public void SetLogicalPlayer(LogicalPlayerIndex player, PlayerIndex gamepad)
        {
            if (player != LogicalPlayerIndex.Any)
            {
                RemoveLogicalPlayer(player);
                _logicalPlayers.Add(player, gamepad);
            }
        }

        /// <summary>
        /// Returns whether a logical player is bound to a gamepad.
        /// </summary>
        /// <param name="player">The player</param>
        public bool IsPlayerBound(LogicalPlayerIndex player)
        {
            return _logicalPlayers.ContainsKey(player);
        }

        /// <summary>
        /// Returns whether a logical player is bound to the specified gamepad.
        /// </summary>
        /// <param name="player">The player</param>
        /// <param name="gamepad">The Gamepad</param>
        public bool IsPlayerBound(LogicalPlayerIndex player, PlayerIndex gamepad)
        {
            if (_logicalPlayers.ContainsKey(player) && _logicalPlayers[player] == gamepad)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        ///  Removes the player-gamepad binding that is associated with 
        ///  the logical player.
        /// </summary>
        public void RemoveLogicalPlayer(LogicalPlayerIndex player)
        {
            if(_logicalPlayers.ContainsKey(player))
                _logicalPlayers.Remove(player);
        }

        /// <summary>
        ///  Removes all the player-gamepad bindings
        /// </summary>
        public void ClearLogicalPlayers()
        {
            _logicalPlayers.Clear();
        }

        /// <summary>
        ///  Adds an InputCommand and makes it pollable
        ///  through the CommandActive-method.
        /// </summary>
        /// <param name="id">The command identifier. Use a constant or enum like named for example "jump".</param>
        /// <param name="command">The InputCommand you wish to add.</param>
        public void AddCommand(string id, InputCommand command)
        {
            if (_commands.ContainsKey(id))
                RemoveCommand(id);
            _commands.Add(id, command);
        }

        /// <summary>
        ///  Removes an added InputCommand.
        /// </summary>
        /// <param name="id">The command identifier.</param>
        public void RemoveCommand(string id)
        {
            if (_commands.ContainsKey(id))
            {
                _commands.Remove(id);
            }
        }

        /// <summary>
        ///  Clears all the added InputCommands.
        /// </summary>
        public void ClearCommands() {
            _commands.Clear();
        }

        /// <summary>
        ///  Poll if the given InputCommand is active.
        /// </summary>
        /// <param name="id">The command identifier.</param>
        public bool CommandActive(string id)
        {
            if (_commands.ContainsKey(id))
            {
                return _commands[id].IsActive;
            }

          return false;
        }

        /// <summary>
        ///  A method to retrieve the states of a gamepads analog sticks.
        /// </summary>
        /// <param name="player">The logical player of the gamepad.</param>
        public GamePadThumbSticks GetSticksState(LogicalPlayerIndex player)
        {
            if (_logicalPlayers.ContainsKey(player)){
                return _gamepadStates[_logicalPlayers[player]].ThumbSticks;
            }

            return new GamePadThumbSticks();
        }

        /// <summary>
        ///  A method to retrieve the states of a gamepads analog sticks.
        /// </summary>
        /// <param name="gamepad">The gamepad.</param>
        public GamePadThumbSticks GetSticksState(PlayerIndex gamepad)
        {
            if (_gamepadStates.ContainsKey(gamepad))
            {
                return _gamepadStates[gamepad].ThumbSticks;
            }
            
            return new GamePadThumbSticks();
        }

        /// <summary>
        ///  A method to retrieve the previous states of a gamepads analog sticks.
        /// </summary>
        /// <param name="player">The logical player of the gamepad to poll from</param>
        public GamePadThumbSticks GetPrevSticksState(LogicalPlayerIndex player)
        {
            if (_logicalPlayers.ContainsKey(player))
            {
                return _prevGamepadStates[_logicalPlayers[player]].ThumbSticks;
            }

            return new GamePadThumbSticks();
        }

        /// <summary>
        ///  A method to retrieve the previous states of a gamepads analog sticks.
        /// </summary>
        /// <param name="gamepad">The gamepad to poll from</param>
        public GamePadThumbSticks GetPrevSticksState(PlayerIndex gamepad)
        {
            if (_gamepadStates.ContainsKey(gamepad))
            {
                return _prevGamepadStates[gamepad].ThumbSticks;
            }
            return new GamePadThumbSticks();
        }

        /// <summary>
        ///  Poll if a given input is down
        /// </summary>
        /// <param name="key"> A keyboard key to poll</param>
        public bool IsDown(Keys key)
        {
            return _pressedKeys.ContainsKey(key.GetHashCode());
        }

        /// <summary>
        ///  Poll if a given input is down
        /// </summary>
        /// <param name="button"> A gamepad button to poll</param>
        ///  <param name="gamepad"> The gamepad to poll from</param>
        public bool IsDown(Buttons button, PlayerIndex gamepad)
        {
            return _gamepadStates.ContainsKey(gamepad) && _gamepadStates[gamepad].IsButtonDown(button);
        }

        /// <summary>
        ///  Poll if a given input is down
        /// </summary>
        /// <param name="button"> A gamepad button to poll</param>
        ///  <param name="player"> The logical player of a gamepad to poll from</param>
        public bool IsDown(Buttons button, LogicalPlayerIndex player)
        {
            if (player == LogicalPlayerIndex.Any)
            {
                for(PlayerIndex i = PlayerIndex.One; i <= PlayerIndex.Four; i++)
                {
                    if (IsDown(button, i))
                        return true;
                }
                return false;
            }
            if (_logicalPlayers.ContainsKey(player))
            {
                return IsDown(button, _logicalPlayers[player]);
            }

            return false;
        }

        /// <summary>
        ///  Poll if a given input is up
        /// </summary>
        /// <param name="key"> A keyboard key to poll</param>
        public bool IsUp(Keys key)
        {
            return !_pressedKeys.ContainsKey(key.GetHashCode());
        }

        /// <summary>
        ///  Poll if a given input is up
        /// </summary>
        /// <param name="button"> A gamepad button to poll</param>
        /// <param name="gamepad"> The gamepad to poll from</param>
        public bool IsUp(Buttons button, PlayerIndex gamepad)
        {
            return _gamepadStates.ContainsKey(gamepad) && _gamepadStates[gamepad].IsButtonUp(button);
        }

        /// <summary>
        ///  Poll if a given input is up
        /// </summary>
        /// <param name="button"> A gamepad button to poll</param>
        /// <param name="player"> The logical player of a gamepad to poll from</param>
        public bool IsUp(Buttons button, LogicalPlayerIndex player)
        {
            if (player == LogicalPlayerIndex.Any)
            {
                for (PlayerIndex i = PlayerIndex.One; i <= PlayerIndex.Four; i++)
                {
                    if (IsUp(button, i))
                        return true;
                }
                return false;
            }
            if (_logicalPlayers.ContainsKey(player))
            {
                return IsUp(button, _logicalPlayers[player]);
            }
            return false;
        }

        /// <summary>
        ///  Poll if a given input have been pressed,
        ///  the input is down during this update and was
        ///  up during the last.
        /// </summary>
        /// <param name="key">The keyboard key to poll</param>
        public bool IsPressed(Keys key)
        {
            return _pressedKeys.ContainsKey(key.GetHashCode()) && !_prevPressedKeys.ContainsKey(key.GetHashCode());
        }

        /// <summary>
        ///  Poll if a given input have been pressed,
        ///  the input is down during this update and was
        ///  up during the last.
        /// </summary>
        /// <param name="button">The gamepad button to poll</param>
        /// <param name="gamepad">The gamepad to poll from</param>
        public bool IsPressed(Buttons button, PlayerIndex gamepad)
        {
            return
                _gamepadStates.ContainsKey(gamepad) && _gamepadStates[gamepad].IsButtonDown(button)
                && _prevGamepadStates.ContainsKey(gamepad) && _prevGamepadStates[gamepad].IsButtonUp(button);
        }

        /// <summary>
        ///  Poll if a given input have been pressed,
        ///  the input is down during this update and was
        ///  up during the last.
        /// </summary>
        /// <param name="button">The gamepad button to poll</param>
        /// <param name="player">The logical player of a gamepad to poll from</param>
        public bool IsPressed(Buttons button, LogicalPlayerIndex player)
        {
            if (player == LogicalPlayerIndex.Any)
            {
                for (PlayerIndex i = PlayerIndex.One; i <= PlayerIndex.Four; i++)
                {
                    if (IsPressed(button, i))
                        return true;
                }
                return false;
            }
            if (_logicalPlayers.ContainsKey(player))
            {
                return IsPressed(button, _logicalPlayers[player]);
            }
            return false;
        }

        /// <summary>
        ///  Poll if a given input is being held,
        ///  the input is down during this update
        ///  and was down during the last.
        /// </summary>
        /// <param name="key">The keyboard key to poll</param>
        public bool IsHeld(Keys key)
        {
            return _pressedKeys.ContainsKey(key.GetHashCode()) && _prevPressedKeys.ContainsKey(key.GetHashCode());
        }

        /// <summary>
        ///  Poll if a given input is being held,
        ///  the input is down during this update
        ///  and was down during the last.
        /// </summary>
        /// <param name="button">The gamepad button to poll</param>
        /// <param name="gamepad">The gamepad to poll from</param>
        public bool IsHeld(Buttons button, PlayerIndex gamepad)
        {
            return
                _gamepadStates.ContainsKey(gamepad) && _gamepadStates[gamepad].IsButtonDown(button)
                && _prevGamepadStates.ContainsKey(gamepad) && _prevGamepadStates[gamepad].IsButtonDown(button);
        }

        /// <summary>
        ///  Poll if a given input is being held,
        ///  the input is down during this update
        ///  and was down during the last.
        /// </summary>
        /// <param name="button">The gamepad button to poll</param>
        /// <param name="player">The logical player of a gamepad to poll from</param>
        public bool IsHeld(Buttons button, LogicalPlayerIndex player)
        {
            if (player == LogicalPlayerIndex.Any)
            {
                for (PlayerIndex i = PlayerIndex.One; i <= PlayerIndex.Four; i++)
                {
                    if (IsHeld(button, i))
                        return true;
                }
                return false;
            }
            if (_logicalPlayers.ContainsKey(player))
            {
                return IsHeld(button, _logicalPlayers[player]);
            }
            return false;
        }

        /// <summary>
        ///  Poll if a given input have been released,
        ///  the input is up during this update
        ///  and was down during the last.
        /// </summary>
        /// <param name="key">The keyboard key to poll</param>
        public bool IsReleased(Keys key)
        {
            return !_pressedKeys.ContainsKey(key.GetHashCode()) && _prevPressedKeys.ContainsKey(key.GetHashCode());
        }

        /// <summary>
        ///  Poll if a given input have been released,
        ///  the input is up during this update
        ///  and was down during the last.
        /// </summary>
        /// <param name="button">The gamepad button to poll</param>
        /// <param name="gamepad">The gamepad to poll from</param>
        public bool IsReleased(Buttons button, PlayerIndex gamepad)
        {
            return
                _gamepadStates.ContainsKey(gamepad) && _gamepadStates[gamepad].IsButtonUp(button)
                && _prevGamepadStates.ContainsKey(gamepad) && _prevGamepadStates[gamepad].IsButtonDown(button);
        }

        /// <summary>
        ///  Poll if a given input have been released,
        ///  the input is up during this update
        ///  and was down during the last.
        /// </summary>
        /// <param name="button">The gamepad button to poll</param>
        /// <param name="player"></param>
        /// <pparam name="player">The logical player of a gamepad to poll from</pparam>
        public bool IsReleased(Buttons button, LogicalPlayerIndex player)
        {
            if (player == LogicalPlayerIndex.Any)
            {
                for (PlayerIndex i = PlayerIndex.One; i <= PlayerIndex.Four; i++)
                {
                    if (IsReleased(button, i))
                        return true;
                }
                return false;
            }
            if (_logicalPlayers.ContainsKey(player))
            {
                return IsReleased(button, _logicalPlayers[player]);
            }
            return false;
        }
    }
}

